/**
 * @license
 * Copyright Google Inc. All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */


export class NgZoneImpl {
  static isInAngularZone(): boolean { return Zone.current.get('isAngularZone') === true; }

  /** @internal */
  private outer: Zone;
  /** @internal */
  private inner: Zone;

  private onEnter: () => void;
  private onLeave: () => void;
  private setMicrotask: (hasMicrotasks: boolean) => void;
  private setMacrotask: (hasMacrotasks: boolean) => void;
  private onError: (error: any) => void;

  constructor({trace, onEnter, onLeave, setMicrotask, setMacrotask, onError}: {
    trace: boolean,
    onEnter: () => void,
    onLeave: () => void,
    setMicrotask: (hasMicrotasks: boolean) => void,
    setMacrotask: (hasMacrotasks: boolean) => void,
    onError: (error: any) => void
  }) {
    this.onEnter = onEnter;
    this.onLeave = onLeave;
    this.setMicrotask = setMicrotask;
    this.setMacrotask = setMacrotask;
    this.onError = onError;

    if (typeof Zone == 'undefined') {
      throw new Error('Angular requires Zone.js prolyfill.');
    }
    Zone.assertZonePatched();
    this.outer = this.inner = Zone.current;
    if ((Zone as any)['wtfZoneSpec']) {
      this.inner = this.inner.fork((Zone as any)['wtfZoneSpec']);
    }
    if (trace && (Zone as any)['longStackTraceZoneSpec']) {
      this.inner = this.inner.fork((Zone as any)['longStackTraceZoneSpec']);
    }
    this.inner = this.inner.fork({
      name: 'angular',
      properties: <any>{'isAngularZone': true},
      onInvokeTask: (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task,
                     applyThis: any, applyArgs: any): any => {
        try {
          this.onEnter();
          return delegate.invokeTask(target, task, applyThis, applyArgs);
        } finally {
          this.onLeave();
        }
      },


      onInvoke: (delegate: ZoneDelegate, current: Zone, target: Zone, callback: Function,
                 applyThis: any, applyArgs: any[], source: string): any => {
        try {
          this.onEnter();
          return delegate.invoke(target, callback, applyThis, applyArgs, source);
        } finally {
          this.onLeave();
        }
      },

      onHasTask:
          (delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => {
            delegate.hasTask(target, hasTaskState);
            if (current === target) {
              // We are only interested in hasTask events which originate from our zone
              // (A child hasTask event is not interesting to us)
              if (hasTaskState.change == 'microTask') {
                this.setMicrotask(hasTaskState.microTask);
              } else if (hasTaskState.change == 'macroTask') {
                this.setMacrotask(hasTaskState.macroTask);
              }
            }
          },

      onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
        delegate.handleError(target, error);
        this.onError(error);
        return false;
      }
    });
  }

  runInner(fn: () => any): any { return this.inner.run(fn); };
  runInnerGuarded(fn: () => any): any { return this.inner.runGuarded(fn); };
  runOuter(fn: () => any): any { return this.outer.run(fn); };
}
